package com.qyb.base.java8.chap4;



import com.qyb.base.java8.bean.Dish;

import java.util.*;
import java.util.stream.Stream;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

/*
流是Java API的新成员，它允许你以声明性方式处理数据集合（通过查询语句来表达，而不是临时编写一个实现），
就现在来说，你可以把它们看成遍历数据集的高级迭代器。

流可以透明地并行处理，你无需写任何多线程代码了！

Java 8中的Stream API可以让你写出这样的代码：
    声明性——更简洁，更易读
    可复合——更灵活
    可并行——性能更好

多模式，如筛选、切片、查找、匹配、映射和归约;

Java 8中的集合支持一个新的stream方法，它会返回一个流（接口定义在java.util.stream.Stream里）,
还有很多其他的方法可以得到流。

流到底是什么呢？简短的定义就是“从支持数据处理操作的源生成的元素序列”。
    元素序列——就像集合一样，流也提供了一个接口，可以访问特定元素类型的一组有序
    值。因为集合是数据结构，所以它的主要目的是以特定的时间/空间复杂度存储和访问元
    素（如ArrayList 与 LinkedList）。但流的目的在于表达计算，比如你前面见到的
    filter、sorted和map。集合讲的是数据，流讲的是计算。我们会在后面几节中详细解
    释这个思想。
    源——流会使用一个提供数据的源，如集合、数组或输入/输出资源。 请注意，从有序集
    合生成流时会保留原有的顺序。由列表生成的流，其元素顺序与列表一致。
    数据处理操作——流的数据处理功能支持类似于数据库的操作，以及函数式编程语言中
    的常用操作，如filter、map、reduce、find、match、sort等。流操作可以顺序执
    行，也可并行执行。筛选（filter）、提取（map）或截断（limit）功能

    流水线——很多流操作本身会返回一个流，这样多个操作就可以链接起来，形成一个大
    的流水线。这让我们下一章中的一些优化成为可能，如延迟和短路。流水线的操作可以
    看作对数据源进行数据库式查询。
    内部迭代——与使用迭代器显式迭代的集合不同，流的迭代操作是在背后进行的。

 */
public class StreamBasic {

    public static void demo1(){
        // Java 7
        getLowCaloricDishesNamesInJava7(Dish.menu).forEach(System.out::println);

        System.out.println("---");

        // Java 8
        getLowCaloricDishesNamesInJava8(Dish.menu).forEach(System.out::println);

        //按照Map里面的类别对菜肴进行分组。
        Map<Dish.Type, List<Dish>> dishesByType =
                Dish.menu.stream().collect(groupingBy(Dish::getType));
        System.out.println("---");


        List<String> threeHighCaloricDishNames =
                Dish.menu                                  //从menu获得流（菜肴列表）  数据源，它给流提供一个元素序列
                        .stream()                           //建立操作流水线：    数据处理操作：filter、map、limit和collect，除了collect之外，所有这些操作都会返回另一个流
                        .filter(d -> d.getCalories() > 300) //首先选出高热量的菜肴  接受Lambda，从流中排除某些元素
                        .map(Dish::getName)                 //获取菜名              接受一个Lambda，将元素转换成其他形式或提取信息。
                        .limit(3)                           //只选择头三个          截断流，使其元素不超过给定数量。
                        .collect(toList());                 //将结果保存在另一 个List中，collect操作开始处理流水线，并返回结果。将流转换为其他形式。
                                                            // 在调用collect之前，没有任何结果产生，实际上根本就没有从menu里选择元素。
                                                            // 你可以这么理解：链中的方法调用都在排队等待，直到调用collect。
        System.out.println(threeHighCaloricDishNames);      //结果是[pork, beef,chicken]


        List<String> title = Arrays.asList("Java8", "In", "Action");
        Stream<String> s = title.stream();
        s.forEach(System.out::println);
        //s.forEach(System.out::println);//java.lang.IllegalStateException:流已被操作或关闭,只能遍历一次;遍历完之后，我们就说这个流已经被消费掉了。所以要记得，流只能消费一次！
    }

    public static List<String> getLowCaloricDishesNamesInJava7(List<Dish> dishes){
        List<Dish> lowCaloricDishes = new ArrayList<>();
        for(Dish d: dishes){
            if(d.getCalories() < 400){
                lowCaloricDishes.add(d);     //用累加器筛选元素
            }
        }
        List<String> lowCaloricDishesName = new ArrayList<>();
        Collections.sort(lowCaloricDishes, new Comparator<Dish>() {//用匿名类对菜肴排序
            public int compare(Dish d1, Dish d2){
                return Integer.compare(d1.getCalories(), d2.getCalories());
            }
        });
        for(Dish d: lowCaloricDishes){
            lowCaloricDishesName.add(d.getName());//处理排序后的菜名列表
        }
        return lowCaloricDishesName;
    }

    public static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes){
        /*
        好处：
            1 代码是以声明性方式写的：说明想要完成什么（筛选热量低的菜肴）而不是说明如何实现一个操作（利用循环和if条件等控制流语句）。
            2 你可以把几个基础操作链接起来，来表达复杂的数据处理流水线（在filter后面接上sorted、map和collect操作）
        */
        return dishes.stream()  //为了利用多核架构并行执行这段代码，你只需要把stream()换成parallelStream()：
                .filter(d -> d.getCalories() < 400)          //选出400卡路里以下的菜肴
                .sorted(comparing(Dish::getCalories))        //按照卡路 里排序
                .map(Dish::getName)                          //提取菜肴的名称
                .collect(toList());                          //将所有名称保存在List中
    }

    public static void demo2(){
        /*
            遍历完之后，我们就说这个流已经被消费掉了。所以要记得，流只能消费一次！
         */
        List<String> title = Arrays.asList("Java8", "In", "Action");
        Stream<String> s = title.stream();
        s.forEach(System.out::println);
        //s.forEach(System.out::println);//java.lang.IllegalStateException:流已被操作或关闭,只能遍历一次;

        /*
            外部迭代与内部迭代:
                使用Collection接口需要用户去做迭代（比如用for-each），这称为外部迭代。
                Streams库使用内部迭代——它帮你把迭代做了，还把得到的流值存在了某个地方，你只要给出一个函数说要干什么就可以了。
         */
        //集合：用for-each循环外部迭代,for-each结构是一个语法糖，它背后的东西用Iterator对象表达出来更要丑陋得多。
        List<String> names = new ArrayList<>();
        for(Dish d: Dish.menu){
            names.add(d.getName());
        }
        System.out.println(names);
        //集合：用背后的迭代器做外部迭代
        List<String> names2 = new ArrayList<>();
        Iterator<Dish> iterator = Dish.menu.iterator();
        while(iterator.hasNext()) {
            Dish d = iterator.next();
            names2.add(d.getName());
        }
        System.out.println(names2);
        //流：内部迭代
        List<String> names3 = Dish.menu.stream()
                .map(Dish::getName)
                .collect(toList());
        System.out.println(names3);
    }

    public static void demo3(){
        //filter、map和limit可以连成一条流水线；可以连接起来的流操作称为中间操作;
        //collect触发流水线执行并关闭它,关闭流的操作称为终端操作。
        List<String> names = Dish.menu.stream()     //从菜单获得流
                .filter(d -> d.getCalories() > 800) //中间操作
                .map(Dish::getName)                 //中间操作
                .limit(3)                           //中间操作
                .collect(toList());                 //将Stream转换为List
        System.out.println(names);//[]
        /*
        中间操作
        诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查
        询。重要的是，除非流水线上触发一个终端操作，否则中间操作不会执行任何处理——它们很懒。
        这是因为中间操作一般都可以合并起来，在终端操作时一次性全部处理。

        终端操作
        终端操作会从流的流水线生成结果。其结果是任何不是流的值，比如List、Integer，甚至void。
        */
        long count = Dish.menu.stream()
                .filter(d -> d.getCalories() > 1000)
                .distinct()
                .limit(3)
                .count();
        System.out.println(count);//0

        /*使用流
            流的使用一般包括三件事：
                 一个数据源（如集合）来执行一个查询；
                 一个中间操作链，形成一条流的流水线；
                 一个终端操作，执行流水线，并能生成结果。


中间操作
操 作          类 型        返回类型             操作参数          函数描述符
filter         中间         Stream<T>            Predicate<T>       T -> boolean
map            中间         Stream<R>            Function<T, R>     T -> R
limit          中间         Stream<T>
sorted         中间         Stream<T>            Comparator<T>      (T, T) -> int
distinct       中间         Stream<T>
终端操作
操 作          类 型         目 的
forEach        终端          消费流中的每个元素并对其应用Lambda。这一操作返回void
count          终端          返回流中元素的个数。这一操作返回long
collect        终端          把流归约成一个集合，比如List、Map甚至是Integer。详见第6章




         */

    }
}
